/////////////////////////////////////////////////////////////////////////////////
//      OpenDNS Updater
//      Copyright (C) 2008 Jason Strese
//
//      This program is free software: you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation, either version 3 of the License, or
//      (at your option) any later version.
//
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//
//      You should have received a copy of the GNU General Public License
//      along with this program.  If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace OpenDNSUpdater
{
    #region Main Form
    public partial class Form1 : Form
    {
        #region Form Events
        //
        // Initializer
        //
        public Form1()
        {
            InitializeComponent();
        }

        //
        // Initial Setup
        //
        private void Form1_Load(object sender, EventArgs e)
        {
            this.chkRemember.CheckState = OpenDNSUpdater.Properties.Settings.Default.OPEN_REMEMBER;

            if(this.chkRemember.CheckState.Equals(CheckState.Checked) && OpenDNSUpdater.Properties.Settings.Default.OPEN_PASS.Length > 0 && OpenDNSUpdater.Properties.Settings.Default.OPEN_USER.Length > 0)
            {
                this.txtUsername.Text = OpenDNSUpdater.Properties.Settings.Default.OPEN_USER;
                this.txtPassword.Text = OpenDNSUpdater.Properties.Settings.Default.OPEN_PASS;

                //
                // The user obviously wants this to operate on its own, if Remember Me is checked.
                // Lets start the updating as soon as we launch the program.
                //

                //
                // Start the updater
                //
                this.cmdBegin.PerformClick();
            }
            else
            {
                MessageBox.Show(this, "You need to enter a username and password.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
            }
        }

        //
        // Integrates accident-free closing, the user must right-click on the notification icon to exit the program
        //
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //
            // Check to see if it's a genuine exit
            //
            if (!this.RealExit)
            {
                //
                // Cancel the close action, it wasn't "real"
                //
                e.Cancel = true;

                //
                // Send the program to the taskbar (aka hide it)
                //
                this.Hide();
            }
        }
        #endregion

        #region Private Declarations
        //
        // Create a network credential variable for future use
        //
        private System.Net.NetworkCredential Auth = new System.Net.NetworkCredential(string.Empty, string.Empty);

        //
        // Initialize the Downloader class
        // - Set the URI
        // - Set file destination
        // - Credentials are NULL by default
        //
        private Downloader Retriever = new Downloader(new Uri(OpenDNSUpdater.Properties.Settings.Default.OPEN_URL), string.Empty, null);

        //
        // Decides whether the user really meant to close the program or not
        //
        private bool RealExit = false;

        //
        // Stores the last IP
        //
        private string LastIP = string.Empty;

        //
        // The updater thread
        //
        private System.Threading.Thread _Thread = null;
        #endregion

        #region Private Functions
        private void cmdBegin_Click(object sender, EventArgs e)
        {
            if (this.cmdBegin.Text.Equals("Begin"))
            {
                //
                // Check to see if they supplied a username and password
                //
                if (this.txtUsername.Text.Length > 0 && this.txtPassword.Text.Length > 0)
                {
                    //
                    // Set the appropriate credentials
                    //
                    this.Auth.UserName = this.txtUsername.Text;
                    this.Auth.Password = this.txtPassword.Text;
                    this.Retriever.Credentials = this.Auth;

                    //
                    // Handle the request (thread)
                    //
                    this._Thread = new System.Threading.Thread(new System.Threading.ThreadStart(CacheHandler));
                    this._Thread.Name = "CacheHandler";
                    this._Thread.IsBackground = true;
                    this._Thread.SetApartmentState(System.Threading.ApartmentState.STA);
                    this._Thread.Start();

                    //
                    // Hand the ButtonTimer (to prevent flood)
                    //
                    new System.Threading.Thread(new System.Threading.ThreadStart(ButtonTimer)).Start();

                    //
                    // Update the two text boxes
                    //
                    this.txtUsername.Enabled = false;
                    this.txtPassword.Enabled = false;

                    //
                    // Update the caption
                    //
                    this.cmdBegin.Text = "Stop";
                }
                else
                {
                    MessageBox.Show(this, "You need to enter a username and password.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
                }
            }
            else
            {
                //
                // Abort the thread
                //
                this._Thread.Abort();

                //
                // Set the thread to NULL
                //
                this._Thread = null;

                //
                // Update the two text boxes
                //
                this.txtUsername.Enabled = true;
                this.txtPassword.Enabled = true;

                //
                // Update the caption
                //
                this.cmdBegin.Text = "Begin";
            }
        }

        //
        // Retrieve string from byte array
        //
        private string getByteArray(byte[] array)
        {
            System.Text.ASCIIEncoding encoder = new System.Text.ASCIIEncoding();
            return encoder.GetString(array);
        }

        //
        // Put string into byte array
        //
        private byte[] toByteArray(string data)
        {
            System.Text.ASCIIEncoding encoder = new System.Text.ASCIIEncoding();
            return encoder.GetBytes(data);
        }

        #endregion

        #region Threads

        #region Delegates
        //
        // Delegates
        //
        private delegate void AddResponseD(string Response);
        private delegate void EDButtonD();

        //
        // Delegate Functions
        //
        private void AddResponse(string Response)
        {
            if (this.lstResponses.InvokeRequired)
            {
                this.lstResponses.Invoke(new AddResponseD(AddResponse), Response);
            }
            else
            {
                this.lstResponses.Items.Add(Response);
            }
        }

        private void EDButton()
        {
            if (this.cmdBegin.InvokeRequired)
            {
                this.cmdBegin.Invoke(new EDButtonD(EDButton), null);
            }
            else
            {
                if (this.cmdBegin.Enabled)
                {
                    this.cmdBegin.Enabled = false;
                }
                else
                {
                    this.cmdBegin.Enabled = true;
                }
            }
        }

        #endregion

        private void ButtonTimer()
        {
            //
            // Disable the button
            //
            this.EDButton();

            //
            // Wait two and a half seconds
            //
            System.Threading.Thread.Sleep(2500);

            //
            // Enabled the button
            //
            this.EDButton();
        }

        //
        // Zero Index a number
        // - 7 to 07
        // - 1 to 01
        // - .. etc
        //
        private string ZeroIndexed(int n)
        {
            return n >= 10 ? n.ToString() : string.Format("0{0}", n);
        }

        private void CacheHandler()
        {
            ////////////////////////
            // Some declarations  //
            ////////////////////////

            //
            // Tell the thread to renew itself
            //
            bool renew = true;

            //
            // Contents of the request
            //
            string[] Contents = { };

            //
            // Loop until terminated
            // - Terminates when the program closes due to it being a background task of the main form
            // OR
            // - Terminates when the user requests it to stop (via the "Stop" button once they've clicked "Begin")
            //
            while (true)
            {
                //
                // Start the download
                //
                this.Retriever.__InitiateDownload_InternalBuffering();

                //
                // Wait until the download finishs to continue
                //
                while (this.Retriever.Busy)
                {
                    System.Threading.Thread.Sleep(100);
                }

                //
                // Attempt to open the file
                //
                try
                {
                    //
                    // Read the internal buffer
                    //
                    Contents = this.Retriever._internalBuffer.Split(new char[] { ' ' });
                    
                    //
                    // Respond to the correct status message
                    //
                    switch(Contents[0])
                    {
                        case "good":
                            this.AddResponse(string.Format("[{1}:{2}:{3}] Successful [{0}]", Contents[1], this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        case "nochg":
                            this.AddResponse(string.Format("[{1}:{2}:{3}] No change was required [{0}]", Contents[1], this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        case "notfqdn":
                            this.AddResponse(string.Format("[{0}:{1}:{2}] Error: Hostname is not a fully-qualified domain name", this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        case "nohost":
                            this.AddResponse(string.Format("[{0}:{1}:{2}] Error: Hostname does not exist in this user account", this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        case "numhost":
                            this.AddResponse(string.Format("[{0}:{1}:{2}] Error: Too many hosts (more than 20) specified", this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        case "abuse":
                            this.AddResponse(string.Format("[{0}:{1}:{2}] Error: Your hostname is blocked for update abuse (shutting down)", this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            renew = false;
                            break;
                        case "badagent":
                            this.AddResponse(string.Format("[{0}:{1}:{2}] Error: User agent not sent or HTTP method not permitted", this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        case "dnserr":
                            this.AddResponse(string.Format("[{0}:{1}:{2}] Error: DNS error encountered", this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        case "911":
                            this.AddResponse(string.Format("[{0}:{1}:{2}] Error: Problem or scheduled maintenance encountered (not your fault)", this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        case "badauth":
                            this.AddResponse(string.Format("[{0}:{1}:{2}] Error: Username and password are invalid", this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                        default:
                            this.AddResponse(string.Format("[{2}:{3}:{4}] Error: Unable to determine response type: '{0} {1}'", Contents[0], Contents[1], this.ZeroIndexed(DateTime.Now.Hour), this.ZeroIndexed(DateTime.Now.Minute), this.ZeroIndexed(DateTime.Now.Second)));
                            break;
                    }

                    //
                    // Determine tooltip (Current IP)
                    //
                    if (Contents[0].Equals("good") || Contents[0].Equals("nochg"))
                    {
                        this.tbarIcon.Text = string.Format("OpenDNS Updater\r\nCurrent IP - {0}", Contents[1]);
                    }
                    else
                    {
                        this.tbarIcon.Text = "OpenDNS Updater\r\nUpdate to determine IP";
                    }
                }
                catch (Exception Ex) // Catch any possible errors
                {
                    //
                    // Report the error just as a response
                    //
                    this.AddResponse(string.Format("We've encountered an error: {0}", Ex.Message));
                }
                finally // The file always ends up getting closed
                {
                    //
                    // Try to close it
                    // - Ignore all errors encountered (they are likely to happen)
                    //
                    try
                    {
                        ///////////////////////////////////////////////////
                        // While we're here, lets free some resources up //
                        ///////////////////////////////////////////////////

                        //
                        // Clear the contents
                        //
                        Contents = null;
                        
                        //
                        // Clear internal buffer
                        //
                        this.Retriever._internalBuffer = string.Empty;

                        //
                        // We [most likely] won't do anything for another hour
                        // - Start the garbage collector now just to get it over with
                        //
                        System.GC.Collect();
                    }
                    catch { }
                }

                //
                // We are only going to do this check every hour
                // - Of course, the user can always click "Update Now" from the icon menu in the taskbar :-)
                // 1 hour = (60^2) * 1000
                //        = 3,600,000ms
                //
                // Note: An exception is raised upon interrupting a sleeping thread, this is silenced by the try method
                //

                if (renew)
                {
                    try
                    {
                        System.Threading.Thread.Sleep(OpenDNSUpdater.Properties.Settings.Default.OPEN_INTERVAL);
                    }
                    catch { }
                }
                else
                {
                    System.Threading.Thread.CurrentThread.Abort();
                }
            }
        }

        #endregion

        #region Click Events
        //
        // Context Menu
        // Button: Exit
        //
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //
            // The user obviously really wants to close the program
            //
            this.RealExit = true;

            //
            // Check to see if they enabled "Remember Me"
            //
            if (this.chkRemember.CheckState.Equals(CheckState.Checked))
            {
                //
                // Update application settings
                //
                OpenDNSUpdater.Properties.Settings.Default.OPEN_USER = txtUsername.Text;
                OpenDNSUpdater.Properties.Settings.Default.OPEN_PASS = txtPassword.Text;
                OpenDNSUpdater.Properties.Settings.Default.OPEN_REMEMBER = CheckState.Checked;
            }
            else
            {
                //
                // Lets make sure all the user information is clear, for their sake
                //
                OpenDNSUpdater.Properties.Settings.Default.Reset();
            }

            //
            // Save the application settings
            //
            OpenDNSUpdater.Properties.Settings.Default.Save();

            //
            // Close the program
            //
            this.Close();
        }

        //
        // Taskbar Icon
        // DoubleClick event
        //
        private void tbarIcon_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            //
            // Show the form
            //
            this.Show();
        }

        //
        // Context Menu
        // Button: Show
        //
        private void showToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //
            // Show the form
            //
            this.Show();
        }

        //
        // Context Menu
        // Button: Update Now
        //
        private void updateNowToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //
            // Check to see if the user has begun the updater yet
            //
            if (this._Thread == null)
            {
                //
                // Start the update process
                // - This will update, too
                // - [Modified] Replaced PerformClick with the actual function of the button
               this.cmdBegin_Click(null, null);

            }
            else
            {
                //
                // This is easy, really - just wake the thread from its slumber!
                // - Note: An exception is thrown when this is called, silenced through the try method
                //
                this._Thread.Interrupt();
            }
        }

        //
        // Link: About
        //
        private void lnkCreateAccount_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            //
            // Show the form telling about OpenDNS
            //
            new frmOpenDNS().ShowDialog();            
        }

        //
        // Link: Clear Responses
        //
        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            //
            // Prompt user asking if they wish to proceed with clearing the response list
            //
            DialogResult _Result = MessageBox.Show("Are you sure you want to clear the responses?\r\n\r\nYou cannot undo this.", "Clear Responses", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);

            //
            // Clear the responses if user selected "Yes"
            // 
            if (_Result.Equals(DialogResult.Yes))
            {
                this.lstResponses.Items.Clear();
            }
        }

        //
        // Button: Options
        // Opens frmOptions
        //
        private void optionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new frmOptions().ShowDialog(this);
        }
        #endregion
    }
    #endregion
}